/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package hhf.flex.ascreator;

import com.sun.mirror.declaration.ClassDeclaration;
import com.sun.mirror.declaration.InterfaceDeclaration;
import com.sun.mirror.declaration.TypeDeclaration;
import com.sun.mirror.util.SimpleDeclarationVisitor;
import hhf.flex.annotations.FlexDestination;
import hhf.flex.annotations.entities.FlexObject;
import hhf.flex.annotations.entities.FlexTransient;
import hhf.flex.ascreator.creators.ASDefaultServiceCreator;
import hhf.flex.ascreator.exceptions.IllegalAnnotationException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;
import javax.ejb.Local;
import javax.ejb.LocalBean;
import javax.ejb.Remote;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author administrateur
 */
// Le visiteur qui se promène sur le code Java
public class Visitor extends SimpleDeclarationVisitor {
	Logger logger = LoggerFactory.getLogger(Visitor.class);

	private final String path;
	private final Map<String, Object> options;

	public Visitor(final String path, final Map<String, Object> options) {
		this.path = path;
		this.options = options;
	}

	/**
	 * Visiteur de classes
	 * @param d
	 */
	@Override
	@SuppressWarnings("unchecked")
	public void visitClassDeclaration(final ClassDeclaration d) {
		FlexDestination flexDestination = d.getAnnotation(FlexDestination.class);
		if (flexDestination != null) {
			visitClassAnnotedFlexDestination(d);
		}
		FlexObject flexObject = d.getAnnotation(FlexObject.class);
		if (flexObject != null) {
			logger.debug("discover FlexObject {}", d.getQualifiedName());
			try {
				Class clazz = Class.forName(d.getQualifiedName());
				ASCreator.discoverASClassFromType(path, clazz, options);
			} catch (ClassNotFoundException ex) {
			}
		}
	}

	/**
	 * Visiteur d'interfaces
	 * @param d
	 */
	@Override
	public void visitInterfaceDeclaration(final InterfaceDeclaration d) {
		FlexDestination flexDestination = d.getAnnotation(FlexDestination.class);
		if (flexDestination != null) {
			visitInterfaceAnnotedFlexDestination(d);
		}
	}

	private void visitClassAnnotedFlexDestination(final TypeDeclaration d) {
		LocalBean localBean = d.getAnnotation(LocalBean.class);
		if(localBean!=null) {
			String className = d.getQualifiedName();
			options.put("typeDeclaration", d);
			logger.debug("discover LocalBean {}", className);
			writePertinentServices(className);
		} else {
			logger.error("An EJB Class (not interface) with FlexDestination annotation have to LocalBean else interface local or Remote have to annoted.");
		}
	}

	private void visitInterfaceAnnotedFlexDestination(final TypeDeclaration d) {
		Local local = d.getAnnotation(Local.class);
		Remote remote = d.getAnnotation(Remote.class);
		if(remote!=null) {
			logger.error("An EJB Interface with FlexDestination annotation should be Local not Remote.");
		}
		if(local==null) {
			logger.warn("An EJB Interface with FlexDestination annotation should be annoted Local but maybe the annotation is on the Bean.");
		}
		String className = d.getQualifiedName();
		options.put("typeDeclaration", d);
		writePertinentServices(className);
	}
	
	/**
	 * Ecrit sur la sortie la destination au format XML
	 * @param id
	 * @param factory
	 * @param source
	 */
	private void writePertinentServices(String id) {
		try {
			Class clazz = Class.forName(id);
			for (Method method : clazz.getMethods()) {
				// on expose pas les methode déprecated
				if (!method.isAnnotationPresent(Deprecated.class)) {
					writePertinentService(path, method, id, options);
				}
			}
//			ASServiceCreator serviceCreator = new ASServiceCreator(path, clazz, id, options);
//			serviceCreator.write();
		} catch (ClassNotFoundException ex) {
		}
	}
	/**
	 * VErifie que la methode doit etre exposé en flex
	 * @param method
	 * @throws IllegalArgumentException 
	 */
	private void checkMethod(Method method) throws IllegalArgumentException {
		if(method.getDeclaringClass().equals(Object.class)) throw new IllegalArgumentException("Object class cannot be a flex destination.");
		if(method.isAnnotationPresent(FlexTransient.class)) throw new IllegalArgumentException("method : "+method.getName()+" is FlexTransient : not exposed.");
		if(method.isAnnotationPresent(Deprecated.class)) throw new IllegalArgumentException("method : "+method.getName()+" is Deprecated : not exposed.");
		if(Modifier.isStatic(method.getModifiers())) throw new IllegalArgumentException("method : "+method.getName()+" is static : not exposed");
	}
	
	@SuppressWarnings("unchecked")
	private void writePertinentService(final String path, final Method method, final String id, final Map<String, Object> options) {
		try {
			checkMethod(method);
			Class<? extends ASServiceCreatorInterface> creatorClass = null;
			ASServiceCreatorInterface ascreator = null;
			boolean needEntityClass = false;
			Annotation[] annos = method.getAnnotations();
			for (Annotation anno : annos) {
				try {
					Method creatorMethod = anno.getClass().getMethod("creator");
					Method needEntityClassMethod = anno.getClass().getMethod("needEntityClass");
					creatorClass = (Class<? extends ASServiceCreatorInterface>) creatorMethod.invoke(anno);
					needEntityClass = (Boolean) needEntityClassMethod.invoke(anno);
					if (needEntityClass) {
						ASMethodClassCreator methodClassCreator = new ASMethodClassCreator(path, method, options);
						methodClassCreator.write();
					}
					ascreator = creatorClass.newInstance();
					break;
				} catch (Exception ex) {
				}
			}
			if (ascreator == null) {
				ascreator = new ASDefaultServiceCreator();
			}
			try {
				ascreator.init(path, method, id, options);
			} catch (IllegalAnnotationException ex) {
				System.out.println(ex);
				return;
			}
			ascreator.write();
		} catch (IllegalArgumentException e) {
		}
	}
}
